package tests;

import static org.junit.Assert.*;

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import util.SafeRingBuffer;

public class RingBufferTest {
	
	private SafeRingBuffer<String> rb;

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		
	}

	@Before
	public void setUp() throws Exception {
		rb = new SafeRingBuffer<String>(10);
	}

	@Test
	public void sizeTest() {
		rb.add("one");
		rb.add("two");
		rb.add("three");
		assertEquals("Size was not three", rb.size(), 3);
	}
	
	@Test
	public void removeTest() {
		rb.add("one");
		rb.add("two");
		rb.add("three");
		rb.remove("two");
		assertEquals("Size was not two", rb.size(), 2);
		rb.remove();
		assertEquals("Size was not one", rb.size(), 1);
		rb.remove();
		try{
			rb.remove();
			fail("should not be able to remove when empty");
		} catch(NoSuchElementException e){}
	}
	
	@Test
	public void containsTest() {
		rb.add("one");
		rb.add("two");
		rb.add("three");
		assertTrue("two was missing", rb.contains("two"));
	}
	
	@Test
	public void oneElementTest() {
		rb.add("one");
		assertEquals("size was not one", 1, rb.size());
		rb.remove();
		assertTrue("size was not 0", rb.isEmpty());
	}

	@Test
	public void addTest(){
		try{
			rb.add(null);
			fail("should not allow null elements");
		} catch (NullPointerException e){}
		for(int i = 1; i < 11; i++){
			rb.add("" + i);
		}
		try{
			rb.add("11");
			fail("Size was only ten");
		} catch (IllegalStateException e){}
	}

	@Test
	public void putTest(){
		try {
			for(int i = 1; i < 11; i++){
				rb.put("" + i);
			}
		} catch (InterruptedException e) {
			fail("interrupted");
		}
		Thread t = new Thread(){
			public void run(){
				try {
					rb.put("11");
				} catch (InterruptedException e) {
					fail("Interrupted");
				}
			}
		};
		t.start();
		try {
			TimeUnit.MILLISECONDS.sleep(100);
		} catch (InterruptedException e) {
			fail("Interrupted");
		}
		rb.remove();
		synchronized(rb){rb.notifyAll();}
	}

	@Test
	public void offerTest(){
		try{
			rb.offer(null);
			fail("should not allow null elements");
		} catch (NullPointerException e){}
		for(int i = 1; i < 11; i++){
			rb.offer("" + i);
		}
		assertFalse("size was only ten", rb.offer("11"));
		
		final State state = new State();
		state.val = true;
		Thread t = new Thread(){
			public void run(){
				try {
					state.setVal(rb.offer("11", 100, TimeUnit.MILLISECONDS));
				} catch (InterruptedException e) {
					fail("Interrupted");
				}
			}
		};
		t.start();
		try {
			TimeUnit.MILLISECONDS.sleep(110);
		} catch (InterruptedException e) {
			fail("Interrupted");
		}
		rb.remove();
		synchronized(rb){rb.notifyAll();}
		try {
			t.join();
		} catch (InterruptedException e) {
			fail("Interrupted");
		}
		assertFalse("RingBuffer should have been full or thread did not execute", state.val);
		
		final State state2 = new State();
		state2.val = false;
		Thread t2 = new Thread(){
			public void run(){
				try {
					state2.setVal(rb.offer("11", 100, TimeUnit.MILLISECONDS));
				} catch (InterruptedException e) {
					fail("Interrupted");
				}
			}
		};
		t2.start();
		try {
			TimeUnit.MILLISECONDS.sleep(10);
		} catch (InterruptedException e) {
			fail("Interrupted");
		}
		rb.remove();
		synchronized(rb){rb.notifyAll();}
		try {
			t2.join();
		} catch (InterruptedException e) {
			fail("Interrupted");
		}
		assertTrue("Offer did not work or thread did not execute", state2.val);
	}
	
	@Test
	public void takeTest(){
		Thread t = new Thread(){
			public void run(){
				try {
					assertEquals("1", rb.take());
				} catch (InterruptedException e) {
					fail("Interrupted");
				}
			}
		};
		t.start();
		try {
			TimeUnit.MILLISECONDS.sleep(100);
		} catch (InterruptedException e) {
			fail("Interrupted");
		}
		rb.add("1");
		synchronized(rb){rb.notifyAll();}
	}
	
	@Test
	public void pollTest(){
		assertTrue("poll should return null when empty", rb.poll() == null);
		rb.offer("1");
		assertTrue("wrong element polled", "1".equals(rb.poll()));
		
		final State state = new State();
		state.val = false;
		Thread t = new Thread(){
			public void run(){
				try {
					state.setVal(rb.poll(100, TimeUnit.MILLISECONDS) == null);
				} catch (InterruptedException e) {
					fail("Interrupted");
				}
			}
		};
		t.start();
		try {
			TimeUnit.MILLISECONDS.sleep(110);
		} catch (InterruptedException e) {
			fail("Interrupted");
		}
		synchronized(rb){rb.notifyAll();}
		try {
			t.join();
		} catch (InterruptedException e) {
			fail("Interrupted");
		}
		assertTrue("poll should return null when timed out", state.val);
		
		final State state2 = new State();
		state2.val = false;
		Thread t2 = new Thread(){
			public void run(){
				try {
					state2.setVal(rb.poll(100, TimeUnit.MILLISECONDS).equals("1"));
				} catch (InterruptedException e) {
					fail("Interrupted");
				}
			}
		};
		t2.start();
		rb.offer("1");
		try {
			t2.join();
		} catch (InterruptedException e) {
			fail("Interrupted");
		}
		assertTrue("Wrong value was polled", state2.val);
	}
	
	@Test
	public void iteratorTest() {
		rb.add("one");
		rb.add("two");
		rb.add("three");
		Iterator<String> it = rb.iterator();
		assertTrue(it.hasNext());
		assertTrue("one".equals(it.next()));
		assertTrue(it.hasNext());
		assertTrue("two".equals(it.next()));
		assertTrue(it.hasNext());
		assertTrue("three".equals(it.next()));
		assertFalse(it.hasNext());
		try{
			it.next();
			fail("should have run out of elements");
		} catch(NoSuchElementException e){}
	}

	public class State{
		private boolean val = false;
		public synchronized void setVal(boolean b){
			val = b;
		}
	}
}
